package ru.spb.nicetu.altx.mnemo.utils;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.NullProgressMonitor;

import ru.spb.nicetu.altx.mnemo.cell.INodeCell;
import ru.spb.nicetu.altx.mnemo.db.valuetype.IBlobToValueConverter;
import ru.spb.nicetu.altx.mnemo.extpoint.valuetypes.IProxyValueType;
import ru.spb.nicetu.altx.mnemo.extpoint.valuetypes.ITemplateValueType;
import ru.spb.nicetu.altx.mnemo.extpoint.valuetypes.ProxyValueType;
import ru.spb.nicetu.altx.mnemo.math.managers.PerformanceManager;
import ru.spb.nicetu.altx.mnemo.math.managers.ValueTypeClassManager;
import ru.spb.nicetu.altx.mnemo.node.DataNode;
import ru.spb.nicetu.altx.mnemo.node.INode;
import ru.spb.nicetu.altx.mnemo.node.UnsafeDataNode;

public class FileValueSaveLoadUtil {
  private static final String NULL_VALUE_FLAG = "NULL_VALUE_FLAG";

  /**
   * Сохраняет Значение в файл.
   * 
   * @param file
   *          - файл, куда сохрняется Значение
   * @param value
   *          - Значение (может быть null)
   * @param valueclassName
   *          - имя класса сохраняемого Значения
   * @throws IOException
   * @throws CoreException
   */
  @SuppressWarnings("unchecked")
  public static void Save(IFile file, ITemplateValueType value,
      String valueclassName) throws IOException, CoreException {
    FileOutputStream fos = new FileOutputStream(new File(file.getLocationURI()));
    ByteArrayOutputStream bos = valueToOutputStream(value, valueclassName);

    fos.write(bos.toByteArray());
    fos.flush();
    fos.close();
    file.refreshLocal(IResource.DEPTH_ZERO, new NullProgressMonitor());
  }

  /**
   * TODO rewrite javadoc for LoadProxyValue() Загружает Proxy-Значение из файла
   * 
   * @param file
   *          файл, откуда загружается Значение
   * @return Значение или null
   * @throws FileNotFoundException
   * @throws IOException
   * @throws ClassNotFoundException
   * @throws NullValueException
   *           в файле сохранено "ПустоеЗначение", имя класса Значения записано
   *           в e.getMessage()
   */
  @SuppressWarnings("unchecked")
  public static ITemplateValueType LoadProxyValue(IFile file)
      throws FileNotFoundException, IOException, ClassNotFoundException,
      NullValueException {
    // FIXME VALUE-MEMORY-Problem
    ProxyValueType proxyValue = new ProxyValueType(file);
    return proxyValue;
  }

  /**
   * TODO write javadoc for LoadRealValue
   * 
   * @param file
   * @return
   * @throws FileNotFoundException
   * @throws IOException
   * @throws ClassNotFoundException
   * @throws NullValueException
   */
  @SuppressWarnings("unchecked")
  public static ITemplateValueType LoadRealValue(IFile file)
      throws FileNotFoundException, IOException, ClassNotFoundException,
      NullValueException {
    ObjectInputStream in = new ObjectInputStream(new FileInputStream(new File(
        file.getLocationURI())));

    // 2. считываем имя класса обертки
    Object classNameO = in.readObject();
    if (FileValueSaveLoadUtil.NULL_VALUE_FLAG.equals(classNameO)) {
      classNameO = in.readObject();
      throw new NullValueException((String) classNameO);
      // return classNameO;
    }

    // вычисляем класс обертки
    Class clazz = ValueTypeClassManager.getInstance().getValueTypeClass(
        (String) classNameO);
    if (clazz == null)
      return null;

    // Получаем загрузчик класса обертки.
    // Этот загрузчик знает о классе обернутого значения
    ClassLoader myLoader = clazz.getClassLoader();

    // 3. Считываем из потока размер массива, представляющего объект value
    int size = in.readInt();

    // 4. Cчитываем из потока массив byte[], которым представлен объект value
    byte[] valueToBytes = new byte[size];
    // in.read(valueToBytes);
    for (int i = 0; i <= valueToBytes.length; i = i + 1000) {
      if (valueToBytes.length - i < 1000) {
        in.read(valueToBytes, i, (valueToBytes.length) - i);
      } else {
        in.read(valueToBytes, i, 1000);
      }
    }
    in.close();

    // Переводим массив byte[] в Object
    ObjectInputStreamWithLoader inpS = new ObjectInputStreamWithLoader(
        new ByteArrayInputStream(valueToBytes), myLoader);

    // Считываем само значение value
    ITemplateValueType value = (ITemplateValueType) inpS.readObject();
    inpS.close();

    PerformanceManager.getInstance().registerValue(value);

    return value;
  }

  /**
   * Получение InputStream-а, из которого можно прочитать Значение, сохраненное
   * в "first-ячейке" INode-а.
   */
  @SuppressWarnings("unchecked")
  public static InputStream valueFromNodeToInputStream(INode node) {
    InputStream is = null;

    INodeCell cell = node.getFirstCell();
    if (cell == null) {
      System.out.println("!!! EMPTY CELL in "
          + FileValueSaveLoadUtil.class.getName() + ".setStream!!!");
      return is;
    }
    
    ITemplateValueType value;
    if (node instanceof DataNode) {
      //оптимизация производительности загрузки
      //получаем proxyValue
      value = UnsafeDataNode.getUnsafeDataValue((DataNode) node);  
    } else{
      value = node.getCellValue(cell);
    }
    
    String valueclassName = cell.getValueClassName();
    try {
      is = FileValueSaveLoadUtil.valueToInputStream(value, valueclassName);
    } catch (IOException e) {
      e.printStackTrace();
    }

    return is;
  }

  @SuppressWarnings("unchecked")
  private static ByteArrayOutputStream valueToOutputStream(
      ITemplateValueType value, String valueclassName) throws IOException {

    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    ObjectOutputStream oos = new ObjectOutputStream(bos);

    if (value == null) {
      doWriteNull(oos, valueclassName);
    } else {
      doWriteValue(oos, value);
    }
    return bos;
  }

  @SuppressWarnings("unchecked")
  private static void doWriteValue(ObjectOutputStream oos,
      ITemplateValueType value) throws IOException {
    // если это Прокси, достаем реальное Значение
    if (value instanceof IProxyValueType) {
      value = ((IProxyValueType) value).getOriginalValue();
    }
    // 2. Сохраняем имя класса обертки
    oos.writeObject(value.getClass().getName());

    // Преобразуем объект value в byte[]
    ByteArrayOutputStream bOut = new ByteArrayOutputStream();
    ObjectOutputStream myOut = new ObjectOutputStream(bOut);
    myOut.writeObject(value);
    byte[] valueToBytes = bOut.toByteArray();

    // 3. Сохраняем размер массива, которым представлен объект value
    oos.writeInt(valueToBytes.length);

    // 4. Сохраняем объект value, представленный в виде byte[] в поток
    // oos.write(valueToBytes);
    int readWindowSize = 1000;
    for (int i = 0; i <= valueToBytes.length; i = i + readWindowSize) {
      if (valueToBytes.length - i < readWindowSize) {
        oos.write(valueToBytes, i, (valueToBytes.length) - i);
      } else {
        oos.write(valueToBytes, i, readWindowSize);
      }
      oos.flush();
    }
  }

  private static void doWriteNull(ObjectOutputStream oos, String valueclassName)
      throws IOException {
    oos.writeObject(FileValueSaveLoadUtil.NULL_VALUE_FLAG);
    oos.writeObject(valueclassName);
  }

  @SuppressWarnings("unchecked")
  public static InputStream valueToInputStream(ITemplateValueType value,
      String valueclassName) throws IOException {
    if (value instanceof ProxyValueType) {
      try {
        return ((ProxyValueType) value).getFile().getContents();
      } catch (CoreException e) {
        e.printStackTrace();
      }
    }
    ByteArrayOutputStream bos = valueToOutputStream(value, valueclassName);

    return new ByteArrayInputStream(bos.toByteArray());
  }

  /**
   * Получение значения типа данных из файла
   * 
   * @param filePath
   *          - абсолютный путь к файлу
   * @param converterClazz
   *          - класс конвертера
   * @return значение типа данного
   * @throws MnemoDiagramException
   *           - если файл не найден или ошибка чтения
   * @throws IOException
   * @throws IllegalAccessException
   * @throws InstantiationException
   */
  @SuppressWarnings("unchecked")
  public static ITemplateValueType loadTemplateValuefromFile(String filePath,
      Class converterClazz) throws IOException, InstantiationException,
      IllegalAccessException {
    byte[] blob = null;
    File pFile = new File(filePath);
    FileInputStream bin;
    bin = new FileInputStream(pFile);
    blob = new byte[bin.available()];
    bin.read(blob);
    bin.close();

    IBlobToValueConverter converter = (IBlobToValueConverter) converterClazz
        .newInstance();
    ITemplateValueType resultValue = converter.convertBlobToValue(blob);
    return resultValue;
  }
}
